#pragma once
//提供运行服务
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>

//包含日志和工具类  
#include "../comm/log.hpp"
#include "../comm/util.hpp"

namespace ns_runner
{
//包含日志和工具类  
using namespace ns_util;
using namespace ns_log;

class Runner
{
public:
    Runner() {}
    ~Runner() {}

public:
    //提供设置进程占用资源大小的接口
    static void SetProcLimit(int _cpu_limit, int _mem_limit)
    {
        // 限制(设置)CPU时长
        struct rlimit cpu_rlimit;
        cpu_rlimit.rlim_max = RLIM_INFINITY;
        cpu_rlimit.rlim_cur = _cpu_limit;
        setrlimit(RLIMIT_CPU, &cpu_rlimit);

        // 限制(设置)内存大小
        struct rlimit mem_rlimit;
        mem_rlimit.rlim_max = RLIM_INFINITY;
        mem_rlimit.rlim_cur = _mem_limit * 1024; //转化成为KB 
        setrlimit(RLIMIT_AS, &mem_rlimit);
    }


    /*******************************************
        * 返回值:关心运行是否成功
        * 返回值 > 0: 程序异常了，退出时收到了信号，返回值就是对应的信号编号
        * 返回值 == 0: 正常运行完毕的，结果保存到了对应的临时文件中
        * 返回值 < 0: 内部错误
        * 
        参数:
        * file_name 指明文件名即可，不需要带路径，不需要带后缀 
        * cpu_limit: 该程序运行的时候,可以使用的最大cpu资源上限
        * mem_limit: 该程序运行的时候,可以使用的最大的内存大小(KB)  按字节来看,数字太大了,不好看
        * *****************************************/
    
    static int Run(const std::string &file_name, int cpu_limit, int mem_limit)  
    {
        /*
            ********************************************
            * 程序运行结果：
            * 1. 代码跑完，结果正确
            * 2. 代码跑完，结果不正确
            * 3. 代码没跑完，异常了

            * Run需要考虑代码跑完，结果正确与否吗? 不考虑！
            * 结果正确与否：是由我们的测试用例决定的！在这里我们只考虑：程序是否正确运行完毕

            * 我们必须知道可执行程序是谁？
            * 一个程序在默认启动的时候
            * 标准输入: 不处理 -> 即:不考虑用户自己写测试用例的情况,必须由我们来做
            * 标准输出: 程序运行完成,输出结果是什么
            * 标准错误: 运行时的错误信息
            * ******************************************
            */
        
        //根据文件名称 获取 对应的带路径的临时同名文件
        std::string _execute = PathUtil::Exe(file_name); //带路径的可执行程序文件  ./temp/file_name.exe
        std::string _stdin   = PathUtil::Stdin(file_name);//输入的东西往这个文件读取
        std::string _stdout  = PathUtil::Stdout(file_name);//输出的结果往这个文件写
        std::string _stderr  = PathUtil::Stderr(file_name);//输出的错误往这个文件写
        
        //打开上述的三个文件
        umask(0);
        int _stdin_fd = open(_stdin.c_str(), O_CREAT|O_RDONLY, 0644); //标准输入是要读取的
        int _stdout_fd = open(_stdout.c_str(), O_CREAT|O_WRONLY, 0644);//为了写入
        int _stderr_fd = open(_stderr.c_str(), O_CREAT|O_WRONLY, 0644);//为了写入

        if(_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0){
            LOG(ERROR) << "运行时打开标准文件失败" << "\n";
            return -1; //代表打开文件失败
        }            
        
        pid_t pid = fork();//创建子进程,子进程会继承父进程的文件描述符表
        if (pid < 0) //创建子进程失败
        {
            LOG(ERROR) << "运行时创建子进程失败" << "\n";
            //关掉文件描述符,让主进程退出
            close(_stdin_fd);
            close(_stdout_fd);
            close(_stderr_fd);
            return -2; //代表创建子进程失败
        }
        else if (pid == 0) //child
        {
            //子进程的标准输入标准输出标准错误, 默认是键盘显示器显示器
            //我们想把它们重定向到我们打开的文件
            //将来程序运行之后,运行结果/错误信息都会写入到该文件
            //并不会影响父类
            dup2(_stdin_fd, 0);
            dup2(_stdout_fd, 1);
            dup2(_stderr_fd, 2);

            SetProcLimit(cpu_limit, mem_limit); //设置进程的资源限制
            
            //进程替换     因为此时的可执行程序带路径,所以我们选择带路径的exec*函数
            // ls  == /bin/ls 这样执行也可以,带路径执行
            execl(_execute.c_str()/*我要执行谁*/, _execute.c_str()/*我想在命令行上如何执行该程序*/, nullptr);//最后要以空结尾！
            exit(1);//如果来到这,说明程序替换出错,退出码设为1(实际上我们不关心退出)
        } 
        else //parent
        {
            // 创建子进程,子进程会继承父进程的文件描述符表
            //这里我们把父进程对应的文件描述符关闭,不会影响子进程,因为进程具有独立性
            close(_stdin_fd);
            close(_stdout_fd);
            close(_stderr_fd);
            int status = 0;
            waitpid(pid, &status, 0);//等待子进程退出,只关心退出时结果是否是异常的  0:表示阻塞等待
            // 程序运行异常，一定是因为因为收到了信号！
            LOG(INFO) << "运行完毕, info: " << (status & 0x7F) << "\n";
            return status & 0x7F;
        }
    }
};
}